import sys, os
import httplib, urllib
import random, binascii
import md5
from urlparse import urlparse

from market.chat.httpb import HttpbParse
from market.chat import domish
from market.chat.Jid import internJID
from market.chat.xmpp_stringprep import nodeprep, resourceprep, nameprep
from market.chat import chatSettings


SASL_XMLNS = 'urn:ietf:params:xml:ns:xmpp-sasl'
BIND_XMLNS = 'urn:ietf:params:xml:ns:xmpp-bind'
SESSION_XMLNS = 'urn:ietf:params:xml:ns:xmpp-session'
DIGEST_CNONCE = binascii.b2a_base64('jeann')


class BOSHClient:
    def __init__(self, playerName, betroomName ):
        self.rid = random.randint(0, 10000000)
        resource = playerName+'_'+betroomName + '_bot'
        self.jabberid = internJID( chatSettings.CELIBAX_PLAYER_CHAT_ACCOUNT + chatSettings.CHAT_ADDRESS_EXTENSION + '/'+ resource )
        self.password = chatSettings.CELIBAX_PLAYER_CHAT_PWD

        self.authid = None
        self.sid = None
        self.logged_in = False
        self.headers = {"Content-type": "text/xml", "Accept": "text/xml"}

        self.bosh_service = urlparse( chatSettings.BOSH_SERVICE )
        
    def buildBody(self, child=None):
        """Build a BOSH body."""
        
        body = domish.Element(("http://jabber.org/protocol/httpbind", "body"))
        body['content'] = 'text/xml; charset=utf-8'
        self.rid = self.rid + 1
        body['rid'] = str(self.rid)
        body['sid'] = str(self.sid)
        body['xml:lang'] = 'en'
     
        if child is not None:
            body.addChild(child)

        return body
        
    def sendBody(self, body):
        """Send the body."""
        
        parser = HttpbParse(True)

        # start new session
        if chatSettings.BOSH_SERVICE[:5] == 'https' :
            conn = httplib.HTTPSConnection(self.bosh_service.netloc)
        else:
            conn = httplib.HTTPConnection(self.bosh_service.netloc)

        
        try:
            #print ''
            #print 'send: '
            #print body.toXml()
            conn.request( "POST", self.bosh_service.path, body.toXml(), self.headers )
            response = conn.getresponse()
            if response.status == 200:
                tmp = response.read()
                #print ''
                #print 'receive: '
                #print tmp
                data = tmp# str(response.read())
            else:
                print 'bosh request failed with status ' + str(response.status)+': ' + response.reason
                #raise Exception('bosh request failed with status ' + str(response.status)+': ' + response.reason)

            conn.close()
            return parser.parse(data)

        except Exception as e:
            print e
            raise Exception( 'POST to ' + chatSettings.BOSH_SERVICE + ' failed.' )


    # Create a session
    def startSessionAndAuth(self, hold='1', wait='50'):

        # the all function implement http://xmpp.org/extensions/xep-0206.html#initiate
        
        # create body for the first request
        body = domish.Element(("http://jabber.org/protocol/httpbind", "body"))
        body['content'] = 'text/xml; charset=utf-8'
        body['hold'] = hold
        body['rid'] = str(self.rid)
        body['to'] = self.jabberid.host
        body['wait'] = wait
        body['window'] = '5'
        body['xml:lang'] = 'en'
        body['xmlns:xmpp'] = 'urn:xmpp:xbosh'
        body['xmpp:version']='1.0'
        #http://stackoverflow.com/questions/9153204/xmpp-over-bosh-jappix-com-replies-always-with-empty-body
        body['pidgin_compatible'] = 'true'
        retb, elems = self.sendBody(body)
        if not( retb.hasAttribute('sid') ) :
            raise Exception("failed to connect to the chat server")
        # get the sid when receiving the response of the first message
        self.sid = retb['sid']

        # send another request if necessary
        sendEmpty = ( len(elems) == 0 )
        if not( sendEmpty ) :
            sendEmpty = elems[0].name == 'features'
        if sendEmpty:
            retb, elems = self.sendBody(self.buildBody(""))
        if not( elems[0].name == 'features' ) :
            raise Exception("failed to get the stream features")

        # go ahead and auth
        ## DIGEST-MD5 #######################################################################################
        #http://wiki.xmpp.org/web/SASLandDIGEST-MD5
        auth = domish.Element((SASL_XMLNS, 'auth'))
        auth['mechanism'] = 'DIGEST-MD5'
        retb, elems = self.sendBody(self.buildBody(auth))
        if not( len(elems) == 1 and elems[0].name == 'challenge' ):
            raise Exception("failed to get DIGEST-MD5 chalenge")

        # respond the DIGEST-MD5 chanllenge
        tmp_str = binascii.a2b_base64(str(elems[0]))
        tmp_list = [item.split("=") for item in tmp_str.split(",")]
        tmp_dic = { a[0]: a[1].replace('\"', '') for a in tmp_list}
        digest_uri='xmpp/'+chatSettings.CHAT_SERVER
        authzid  = self.jabberid.user + chatSettings.CHAT_ADDRESS_EXTENSION
        nc = '00000001'
        nonce = tmp_dic['nonce']
        qop = tmp_dic['qop']
        if 'realm' in tmp_dic:
            realm = tmp_dic['realm']
        else:
            realm = ''
        # 1 Create a string of the form "username:realm:password". Call this string X.
        x = self.jabberid.user+':'+realm+':'+self.password
        # 2 Compute the 16 octet MD5 hash of X. Call the result Y.
        y = md5.new( x ).digest()
        # 3 Create a string of the form "Y:nonce:cnonce:authzid". Call this string A1.
        a1 = y+':'+nonce+':'+DIGEST_CNONCE+':'+authzid.encode('utf-8').strip()
        # 4 Create a string of the form "AUTHENTICATE:digest-uri". Call this string A2.
        a2 = 'AUTHENTICATE:'+digest_uri
        # 5 Compute the 32 hex digit MD5 hash of A1. Call the result HA1.
        ha1 = md5.new( a1 ).hexdigest()
        # 6 Compute the 32 hex digit MD5 hash of A2. Call the result HA2.
        ha2 = md5.new( a2 ).hexdigest()
        # 7 Create a string of the form "HA1:nonce:nc:cnonce:qop:HA2". Call this string KD.
        kd = ha1+':'+nonce+':'+nc+':'+DIGEST_CNONCE+ ':'+qop+':'+ha2
        # 8 Compute the 32 hex digit MD5 hash of KD. Call the result Z.                    
        challenge_client_str = 'username="'+ self.jabberid.user +'",'
        challenge_client_str +='realm="'+realm+'",'
        challenge_client_str +='nonce="'+nonce+'",'
        challenge_client_str +='cnonce="'+DIGEST_CNONCE+'",'
        challenge_client_str +='nc='+nc+','
        challenge_client_str +='qop='+qop+','
        challenge_client_str +='digest-uri="'+digest_uri+'",'
        challenge_client_str +='response='+ md5.new( kd ).hexdigest()+','
        challenge_client_str +='charset=utf-8,'
        challenge_client_str +='authzid="'+authzid.encode('utf-8').strip()+'"'
        challenge_client = domish.Element((SASL_XMLNS, 'response'))
        challenge_client.addContent(binascii.b2a_base64(challenge_client_str))
        retb, elems = self.sendBody(self.buildBody(challenge_client))
        success_client = domish.Element((SASL_XMLNS, 'response'))
        retb, elems = self.sendBody(self.buildBody(success_client))
        failure = len(elems) < 1
        if( not( failure ) ):
            failure = elems[0].name != 'success'
        if( failure ):
            raise Exception("failed at the DIGEST-MD5 challenge")

        # restart session
        restartMsg = self.buildBody("")
        restartMsg['to'] = self.jabberid.host
        restartMsg['xmpp:restart'] = 'true'
        restartMsg['xmlns:xmpp']='urn:xmpp:xbosh'
        retb, elems = self.sendBody(restartMsg)
        failure = len(elems) < 1
        if( not( failure ) ):
            failure = elems[0].name != 'features'
        if( failure ):
            raise Exception("failed to get the features after restarting connection")

        # bind
        iq = domish.Element(('jabber:client', 'iq'))
        iq['type'] = 'set'
        iq.addUniqueId()
        iq.addElement('bind')
        iq.bind['xmlns'] = BIND_XMLNS
        if self.jabberid.resource:
            iq.bind.addElement('resource')
            iq.bind.resource.addContent(  self.jabberid.resource )
        retb, elems = self.sendBody(self.buildBody(iq))
        failure = len(elems) < 1
        if( not( failure ) ):
            failure = elems[0].name != 'iq'
        if( not( failure ) ):
            failure = len(elems[0].children) < 1
        if( not( failure ) ):
            failure = elems[0].children[0].name != 'bind'
        if( failure ):
            raise Exception("failed binding the session")


        # send session
        iq = domish.Element(('jabber:client', 'iq'))
        iq['type'] = 'set'
        iq.addUniqueId()
        iq.addElement('session')
        iq.session['xmlns'] = SESSION_XMLNS
        retb, elems = self.sendBody(self.buildBody(iq))

        # send a presence message
        #presence = domish.Element(('jabber:client', 'presence'))
        #retb, elems = self.sendBody(self.buildBody(presence))

        # send a test 'Hi!' message
        #message = domish.Element((None, 'message'))
        #message['type'] = 'chat'
        #message['to'] = 'celibax_betroom@jappix.com'
        #message.addElement('body', content='Hi!')
        #retb, elems = self.sendBody(self.buildBody(message))
              
                    
                    
